#include <algorithm>

#include <time.h>

#include "Scene\Scene.h"

Scene::Scene(Camera* camera)
	: m_vertices(NULL),
	  m_materials(NULL),
	  m_opaque_meshes(NULL),
	  m_transparent_meshes(NULL),
	  m_lights(NULL),
	  m_camera(camera),
	  m_point_lights_behaviour(0)
{
	srand(time(NULL));
}

Scene::~Scene()
{
	for( auto it = m_lights.begin() ; it != m_lights.end() ; ++it)
	{
		delete it->first;
	}

	m_lights.clear();

	for(UINT i = 0 ; i < m_opaque_meshes.size() ; i++)
	{
		delete m_opaque_meshes[i];
	}

	m_opaque_meshes.clear();

	for(UINT i = 0 ; i < m_transparent_meshes.size() ; i++)
	{
		delete m_transparent_meshes[i];
	}

	m_transparent_meshes.clear();

	for(UINT i = 0 ; i < m_materials.size() ; i++)
	{
		delete m_materials[i];
	}

	m_materials.clear();

	m_vertices.clear();

	if(m_vertex_buffer)
	delete m_vertex_buffer;
}

BOOL Scene::build(CONST aiScene* ai_scene)
{
	if(ai_scene->HasMaterials())
	{
		for(UINT i = 0; i < ai_scene->mNumMaterials ; i++)
		{
			Material* material = new Material();

			if(!material->build(ai_scene->mMaterials[i]))
			{
				return FALSE;
			}

			m_materials.push_back(material);
		}
	}
	else
	{
		return FALSE;
	}



	if(ai_scene->HasMeshes())
	{
		for(UINT i = 0 ; i < ai_scene->mNumMeshes ; i++)
		{
			if(ai_scene->mMeshes[i]->mPrimitiveTypes == aiPrimitiveType_TRIANGLE)
			{
				for(UINT j = 0 ; j < ai_scene->mMeshes[i]->mNumVertices ; j++)
				{
					Vertex vertex;

					vertex.position = D3DXVECTOR4(ai_scene->mMeshes[i]->mVertices[j].x,
												  ai_scene->mMeshes[i]->mVertices[j].y,
												  ai_scene->mMeshes[i]->mVertices[j].z,
												  1);

					vertex.normal = D3DXVECTOR3(ai_scene->mMeshes[i]->mNormals[j].x,
												ai_scene->mMeshes[i]->mNormals[j].y,
												ai_scene->mMeshes[i]->mNormals[j].z);

					m_vertices.push_back(vertex);
				}
			}
		}

		m_vertex_buffer = new VertexBuffer(m_vertices.data(),sizeof(Vertex),m_vertices.size());

		UINT interval = 0;
		BOOL mesh_exist_already;
		BOOL mesh_index = 0;

		for(UINT i = 0 ; i < ai_scene->mNumMeshes ; i++)
		{
			if(ai_scene->mMeshes[i]->mPrimitiveTypes == aiPrimitiveType_TRIANGLE)
			{
				UINT mesh_material_id = ai_scene->mMeshes[i]->mMaterialIndex;
				Material* mesh_material = m_materials.at(mesh_material_id);

				if(mesh_material->GetOpacity() == 1.0f)
				{
					mesh_exist_already = FALSE;

					for(UINT j = 0 ; j < m_opaque_meshes.size() ; j++)
					{
						if(m_opaque_meshes.at(j)->GetMaterial() == m_materials[ai_scene->mMeshes[i]->mMaterialIndex])
						{
							mesh_exist_already = TRUE;
							mesh_index = j;
							break;
						}
					}

					if(mesh_exist_already)
					{
						if(!m_opaque_meshes.at(mesh_index)->build(ai_scene->mMeshes[i],interval,mesh_material,m_vertex_buffer))
							return FALSE;

						interval+= ai_scene->mMeshes[i]->mNumVertices;
					}
					else
					{
						Mesh* mesh = new Mesh();

						if(!mesh->build(ai_scene->mMeshes[i],interval,mesh_material,m_vertex_buffer))
							return FALSE;

						m_opaque_meshes.push_back(mesh);
					}
				}
				else
				{
					Mesh* mesh = new Mesh();

					if(!mesh->build(ai_scene->mMeshes[i],interval,mesh_material,m_vertex_buffer))
						return FALSE;

					interval+= ai_scene->mMeshes[i]->mNumVertices;

					m_transparent_meshes.push_back(mesh);
				}
			}
		}
	}
	else
	{
		return FALSE;
	}

	Light* directional_sun = new Light(10.0f, 10.0f, -10.0f, 0.0f,
									   1.0f, 1.0f, -1.0f,
									   D3DX_PIF, D3D10_FLOAT32_MAX,
									   0.3f, 0.3f, 0.3f,
									   0.0f, 0.0f, 0.0f,
									   0.0f, 0.0f, 0.0f);

	std::pair<Light*, Matrix4x4> pair(directional_sun, Matrix4x4::CreateIdentity());
	m_lights.insert(pair);

	return TRUE;
}

VOID Scene::update()
{
	updateLights();
	sortTransparentMeshes();
}

VOID Scene::newLight()
{
	FLOAT r = (rand() % 100) / 100.0f;
	FLOAT g = (rand() % 100) / 100.0f;
	FLOAT b = (rand() % 100) / 100.0f;
	FLOAT angle = (rand() % 20) + 3.0f;
	FLOAT radius = 1.0f +((rand() % 5) + 1) * 0.1f;
	FLOAT y = 0.5f + ((rand() % 20) + 1) * 0.1f;
	FLOAT x = ((rand() % 12) -2) * 1.0f;
	FLOAT z = ((rand() % 14) -9) * 1.0f;

	UINT light_type = 1;//rand() % 2;
	Light* light;

	if(light_type)
	{
		Vector4 direction((rand() % 100 - rand() % 100) / 100.0f, (rand() % 100 - rand() % 100) / 100.0f, (rand() % 100 - rand() % 100) / 100.0f, 0);
		direction.Normalize();

		light = new Light(0.0f, 1.6f, 0.0f, 1.0f,
						  direction.GetX(), direction.GetY(), direction.GetZ(),
						  D3DX_PIF / angle, D3D10_FLOAT32_MAX,
						  r, g, b,
						  r, g, b,
						  r, g, b);
	}
	else
	{
		light = new Light(x, y, z, 1.0f,
						  0.0f, 0.0f, 0.0f,
						  D3DX_PIF, radius,
						  r, g, b,
						  r, g, b,
						  r, g, b);
	}


	Vector4 rotation_axis = Vector4((rand() % 100 - rand() % 100) / 100.0f, (rand() % 100 - rand() % 100) / 100.0f, (rand() % 100 - rand() % 100) / 100.0f);
	std::pair<Light*, Matrix4x4> pair(light, Matrix4x4::CreateRotation(rotation_axis.Normalize(), D3DX_PIF / 10 * Engine::GetUpdateDelay() / 1000));
	m_lights.insert(pair);

}

VOID Scene::deleteLight()
{
	if(m_lights.size() > 1)
	{
		for( auto it = m_lights.begin() ; it != m_lights.end() ; ++it)
		{
			if(it->first->GetType() != Light::DIRECTIONAL)
			{
				m_lights.erase(it);
				return;
			}
		}
	}
}

VertexBuffer& Scene::vertexBuffer() CONST
{
	return *m_vertex_buffer;
}

CONST std::vector<CONST Light*> Scene::lights() CONST
{
	std::vector< CONST Light* > const_lights;
	
	for( auto it = m_lights.begin() ; it != m_lights.end() ; ++it)
	{
		const_lights.push_back(it->first);
	}

	return const_lights;
}

CONST std::vector<Material*>& Scene::materials() CONST
{
	return m_materials;
}

CONST std::vector<Mesh*>& Scene::opaqueMeshes() CONST
{
	return m_opaque_meshes;
}

CONST std::vector<Mesh*>& Scene::transparentMeshes() CONST
{
	return m_transparent_meshes;
}

VOID Scene::updateLights()
{
	++m_point_lights_behaviour;

	if(m_point_lights_behaviour == 50)
		m_point_lights_behaviour = 0;

	for( auto it = m_lights.begin() ; it != m_lights.end() ; ++it)
	{
		if(it->first->GetType() == Light::POINT)
		{
			FLOAT radius = it->first->GetRadius();
			FLOAT diff = 0.5f * Engine::GetUpdateDelay() / 1000;

			if(m_point_lights_behaviour < 25)
			{
				radius += diff;
			}
			else
			{
				radius -= diff;
			}

			it->first->SetRadius(radius);

			Vector4 position = it->first->GetPosition();
			position = it->second * position;
			position.SetW(1.0f);
			it->first->SetPosition(position);
		}

		if(it->first->GetType() == Light::SPOT)
		{
			Vector4 direction = it->first->GetDirection();
			direction = it->second * direction;
			direction.SetW(0);

			it->first->SetDirection(direction);
		}
	}
}

VOID Scene::sortTransparentMeshes()
{
	// Clearing.
	ZeroMemory(m_buckets, sizeof(Mesh*) * BUCKET_COUNT * BUCKET_SIZE);

	// Dealing.
	CONST Camera& camera = Engine::GetInstance().camera(); // World transformation ?
	
	for(std::vector< Mesh* >::iterator it = m_transparent_meshes.begin(); it != m_transparent_meshes.end(); ++it)
	{
		Mesh* mesh = *it;

		FLOAT distance = sqrt(pow(mesh->center().x - camera.GetPosition().GetX(),2) +
							  pow(mesh->center().y - camera.GetPosition().GetY(),2) +
							  pow(mesh->center().z - camera.GetPosition().GetZ(),2));

		// No need to handle meshes outside frustrum.
		if(distance >= camera.Far())
			continue;

		UINT i = static_cast< UINT >(distance / camera.Far() * BUCKET_COUNT);
		UINT j = 0;

		for(j = 0; j < BUCKET_SIZE && m_buckets[i * BUCKET_SIZE + j] != NULL; ++j);

		if(j < BUCKET_SIZE)
			m_buckets[i * BUCKET_SIZE + j] = mesh;
	}

	// Copy.
	m_sorted_transparent_meshes.clear();

	for(INT i = (BUCKET_SIZE * BUCKET_COUNT) - 1; i >= 0; --i)
	{
		if(m_buckets[i] != NULL)
			m_sorted_transparent_meshes.push_back(m_buckets[i]);
	}
}